/**
 * Gitee图床插件
 * 用于将图片上传到Gitee仓库，并返回可访问的URL
 * @version 1.0.0
 * @author 坚果派
 * @description 支持图片上传、删除和自定义配置
 */

// 常量定义
const uploadedName = "gitee";
const domain = "https://gitee.com";
const urlParser = require("url");
const defaultMsg = "picgo commit";

// 错误信息定义
const ERROR_MESSAGES = {
  CONFIG_NOT_FOUND: "未找到上传器配置，请先配置Gitee图床信息",
  UPLOAD_FAILED: "图片上传失败",
  FILE_EXISTS: "文件已经存在",
  DELETE_FAILED: "文件删除失败",
  TOKEN_INVALID: "访问令牌无效或已过期",
  NETWORK_ERROR: "网络请求失败",
  NETWORK_TIMEOUT: "网络连接超时，正在重试...",
  FILE_TOO_LARGE: "文件大小超过限制",
  UNSUPPORTED_TYPE: "不支持的文件类型",
  UPLOAD_CANCELLED: "上传已取消",
  RATE_LIMIT: "接口调用频率限制，请稍后重试",
};

// 添加错误处理工具函数
const handleError = (err, context = {}) => {
  const errorType = err.response?.status === 429 ? 'RATE_LIMIT' :
                   err.code === 'ETIMEDOUT' ? 'NETWORK_TIMEOUT' :
                   err.message.includes('size') ? 'FILE_TOO_LARGE' :
                   'UPLOAD_FAILED';
                   
  ctx.log.error(`[错误]${ERROR_MESSAGES[errorType]}: ${context.fileName || ''}`);
  ctx.log.error(`[详情]${err.message}`);
  
  ctx.emit("notification", {
    title: ERROR_MESSAGES[errorType],
    body: context.fileName ? `文件：${context.fileName}` : err.message
  });
  
  return errorType;
};

/**
 * 插件主模块
 * @param {Object} ctx PicGo上下文
 * @returns {Object} 插件接口
 */
module.exports = (ctx) => {
  /**
   * 注册上传服务
   */
  const register = () => {
    ctx.helper.uploader.register(uploadedName, {
      handle,
      name: "Gitee图床",
      config: config,
    });

    // 监听删除事件
    ctx.on("remove", onRemove);
  };

  /**
   * 获取请求头
   * @returns {Object} HTTP请求头
   */
  const getHeaders = () => ({
    "Content-Type": "application/json;charset=UTF-8",
  });

  /**
   * 验证用户配置的完整性
   * @param {Object} config 用户配置
   * @throws {Error} 配置验证失败时抛出错误
   */
  const validateConfig = (config) => {
    const requiredFields = ['owner', 'repo', 'token'];
    const missingFields = requiredFields.filter(field => !config[field]);

    if (missingFields.length > 0) {
      throw new Error(`缺少必要配置项: ${missingFields.join(', ')}`);
    }
  };

  /**
   * 获取用户配置并处理
   * @returns {Object} 处理后的用户配置
   * @throws {Error} 配置不存在时抛出错误
   */
  const getUserConfig = () => {
    const userConfig = ctx.getConfig("picBed.gitee");

    if (!userConfig) {
      ctx.log.error(ERROR_MESSAGES.CONFIG_NOT_FOUND);
      throw new Error(ERROR_MESSAGES.CONFIG_NOT_FOUND);
    }

    try {
      validateConfig(userConfig);
    } catch (err) {
      ctx.log.error(err.message);
      throw err;
    }

    // 构建API基础URL
    userConfig.baseUrl = `${domain}/api/v5/repos/${userConfig.owner}/${userConfig.repo}`;

    // 构建预览URL
    userConfig.previewUrl = `${domain}/${userConfig.owner}/${userConfig.repo}/raw/master${formatConfigPath(userConfig)}`;

    // 设置提交信息
    userConfig.message = userConfig.message || defaultMsg;

    return userConfig;
  };

  /**
   * 处理图片上传
   * @param {Object} ctx PicGo上下文
   * @returns {Object} 处理后的上下文
   */
  // 在文件开头添加重试相关配置
  const RETRY_CONFIG = {
    maxRetries: 3,
    retryDelay: 1000,
    retryableErrors: ['ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED']
  };
  
  // 添加重试工具函数
  const retry = async (fn, retryCount = 0) => {
    try {
      return await fn();
    } catch (err) {
      if (retryCount < RETRY_CONFIG.maxRetries && 
          RETRY_CONFIG.retryableErrors.some(e => err.message.includes(e))) {
        ctx.log.warn(`[重试]第${retryCount + 1}次重试`);
        await new Promise(resolve => setTimeout(resolve, RETRY_CONFIG.retryDelay));
        return retry(fn, retryCount + 1);
      }
      throw err;
    }
  };
  
  // 在 handle 函数中使用重试机制
  // 添加文件验证配置
  const FILE_CONFIG = {
    maxSize: 5 * 1024 * 1024, // 5MB
    allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
  };
  
  // 添加文件验证函数
  const validateFile = (file) => {
    if (file.size > FILE_CONFIG.maxSize) {
      throw new Error(`文件大小超过限制：${file.fileName}`);
    }
    if (!FILE_CONFIG.allowedTypes.includes(file.type)) {
      throw new Error(`不支持的文件类型：${file.fileName}`);
    }
  };
  
  // 在 handle 函数中使用文件验证
  const handle = async (ctx) => {
    let userConfig;
    try {
      userConfig = getUserConfig();
    } catch (err) {
      ctx.emit("notification", {
        title: "配置错误",
        body: err.message,
      });
      throw err;
    }

    const realUrl = `${userConfig.baseUrl}/contents${formatConfigPath(userConfig)}`;
    ctx.log.info(`[上传]开始上传${ctx.output.length}个文件`);

    for (const item of ctx.output) {
      try {
        // 处理图片数据
        let image = item.buffer;
        if (!image && item.base64Image) {
          image = Buffer.from(item.base64Image, "base64");
        }

        ctx.log.info(`[上传]处理文件: ${item.fileName}`);
        const perRealUrl = `${realUrl}/${item.fileName}`;
        const postConfig = postOptions(perRealUrl, image);

        await retry(async () => {
          await ctx.Request.request(postConfig);
          item.imgUrl = `${userConfig.previewUrl}/${item.fileName}`;
        });
        ctx.log.success(`[上传]文件上传成功: ${item.fileName}`);
      } catch (err) {
        ctx.log.error(`[上传]文件上传失败: ${item.fileName}, 错误: ${err.message}`);

        if (checkIsDuplicateFile(err.message)) {
          ctx.emit("notification", {
            title: ERROR_MESSAGES.UPLOAD_FAILED,
            body: ERROR_MESSAGES.FILE_EXISTS,
          });
          continue;
        }

        // 处理常见错误类型
        const errorBody = err.response?.status === 401 ?
          ERROR_MESSAGES.TOKEN_INVALID :
          err.message.includes('ETIMEDOUT') ?
            ERROR_MESSAGES.NETWORK_ERROR :
            JSON.stringify(err);

        ctx.emit("notification", {
          title: ERROR_MESSAGES.UPLOAD_FAILED,
          body: errorBody,
        });
      } finally {
        // 清理临时数据
        delete item.base64Image;
        delete item.buffer;
      }
    }

    return ctx;
  };

  /**
   * 检查是否为重复文件错误
   * @param {string} message 错误信息
   * @returns {boolean} 是否为重复文件错误
   */
  const checkIsDuplicateFile = (message) =>
    message.includes("A file with this name already exists");

  /**
   * 构建POST请求选项
   * @param {string} url 请求URL
   * @param {Buffer} image 图片数据
   * @returns {Object} 请求选项
   */
  const postOptions = (url, image) => {
    const config = getUserConfig();
    return {
      method: "POST",
      url: encodeURI(url),
      headers: getHeaders(),
      formData: {
        access_token: config.token,
        content: image.toString("base64"),
        message: config.message || defaultMsg,
      },
    };
  };

  /**
   * 处理删除操作
   * @param {Array} files 要删除的文件列表
   */
  const onRemove = async (files) => {
    const rms = files.filter((each) => each.type === uploadedName);
    if (rms.length === 0) return;

    ctx.log.info(`[删除]开始删除${rms.length}个文件`);
    const config = getUserConfig();
    const headers = getHeaders();
    const fail = [];

    for (const file of rms) {
      try {
        ctx.log.info(`[删除]处理文件: ${file.fileName}`);
        const filepath = getFilePath(file.imgUrl);
        const sha = await getSha(filepath).catch((err) => {
          ctx.log.error(`[删除]获取SHA失败: ${JSON.stringify(err)}`);
          throw err;
        });

        const url = `${filepath}?access_token=${config.token}&message=${config.message}&sha=${sha}`;
        ctx.log.info(`[删除]请求地址: ${url}`);

        const opts = {
          method: "DELETE",
          url: encodeURI(url),
          headers,
        };

        await ctx.request(opts);
        ctx.log.success(`[删除]文件删除成功: ${file.fileName}`);
      } catch (err) {
        ctx.log.error(`[删除]文件删除失败: ${file.fileName}, 错误: ${err.message}`);
        fail.push(file);
      }
    }

    ctx.emit("notification", {
      title: "删除提示",
      body: fail.length === 0 ?
        "成功同步删除" :
        `删除失败${fail.length}个，请查看日志了解详情`,
    });
  };

  /**
   * 从图片URL获取文件路径
   * @param {string} url 图片URL
   * @returns {string} 文件路径
   */
  const getFilePath = (url) => {
    const pathInfo = urlParser.parse(url);
    const baseUrl = `${pathInfo.protocol}//${pathInfo.host}`;
    return url
      .replace(baseUrl, `${baseUrl}/api/v5/repos`)
      .replace("raw/master", "contents");
  };

  /**
   * 获取文件的SHA值
   * @param {string} filepath 文件路径
   * @returns {Promise<string>} SHA值
   */
  const getSha = async (filepath) => {
    const config = getUserConfig();
    const opts = {
      method: "GET",
      url: encodeURI(`${filepath}?access_token=${config.token}`),
      headers: getHeaders(),
    };

    const res = await ctx.Request.request(opts);
    return JSON.parse(res).sha;
  };

  /**
   * 格式化配置路径
   * @param {Object} userConfig 用户配置
   * @returns {string} 格式化后的路径
   */
  const formatConfigPath = (userConfig) =>
    userConfig.path ? `/${userConfig.path}` : "";

  /**
   * 配置项
   * @param {Object} ctx PicGo上下文
   * @returns {Array} 配置项列表
   */
  const config = (ctx) => {
    const userConfig = ctx.getConfig("picBed.gitee") || {};

    return [
      {
        name: "owner",
        type: "input",
        default: userConfig.owner,
        required: true,
        message: "owner",
        alias: "owner",
      },
      {
        name: "repo",
        type: "input",
        default: userConfig.repo,
        required: true,
        message: "repo",
        alias: "repo",
      },
      {
        name: "path",
        type: "input",
        default: userConfig.path,
        required: false,
        message: "path;根目录可不用填",
        alias: "path",
      },
      {
        name: "token",
        type: "input",
        default: userConfig.token,
        required: true,
        message: "请输入Gitee的访问令牌",
        alias: "token",
      },
      {
        name: "message",
        type: "input",
        default: userConfig.message,
        required: false,
        message: defaultMsg,
        alias: "message",
      },
    ];
  };

  return {
    uploader: "gitee",
    register,
  };
};
